#include "filters/types.h"
#include "utils/logger.h"

#include <arpa/inet.h>
#include <sstream>

std::optional<IPRange> IPRange::fromCIDR(const std::string &cidr) {
    size_t slash_pos = cidr.find('/');
    if (slash_pos == std::string::npos) {
        return std::nullopt;
    }

    std::string ip_str = cidr.substr(0, slash_pos);
    std::string prefix_str = cidr.substr(slash_pos + 1);

    /// Parse the IP address.
    struct in_addr addr;
    if (inet_pton(AF_INET, ip_str.c_str(), &addr) != 1) {
        return std::nullopt;
    }

    /// Parse prefix length.
    try {
        uint8_t prefix_length = std::stoi(prefix_str);
        if (prefix_length > 32) {
            return std::nullopt;
        }

        IPRange range;
        range.base = ntohl(addr.s_addr);
        range.prefix_length = prefix_length;
        range.mask = (prefix_length == 0) ? 0 : ~((1u << (32 - prefix_length)) - 1);

        range.base &= range.mask;

        return range;
    } catch (std::exception&) {
        return std::nullopt;
    }
}

bool IPRange::contains(uint32_t ip) const { return (ip & mask) == base; }

bool BasicCondition::matches(const Packet& packet) const {
    /// Check protocal.
    if (protocol && packet.isIPv4()) {
        if (packet.ipv4Header()->protocol != *protocol) {
            return false;
        }
    }

    /// Check source ip.
    if (src_ip) {
        uint32_t src_addr = ntohl(packet.ipv4Header()->srcAddr);
        if (!src_ip->contains(src_addr)) {
            return false;
        }
    }

    /// Check destination ip.
    if (dst_ip) {
        uint32_t dst_addr = ntohl(packet.ipv4Header()->dstAddr);
        if (!dst_ip->contains(dst_addr)) {
            return false;
        }
    }

    /// Check source port.
    if (src_port && packet.isTCP()) {
        uint16_t port = ntohs(packet.tcpHeader()->srcPort);
        if (!src_port->contains(port)) {
            return false;
        }
    } else if (src_port && packet.isUDP()) {
        uint16_t port = ntohs(packet.udpHeader()->srcPort);
        if (!src_port->contains(port))
            return false;
    }

    /// Check destination port.
    if (dst_port && packet.isTCP()) {
        uint16_t port = ntohs(packet.tcpHeader()->dstPort);
        if (!dst_port->contains(port)) {
            return false;
        }
    } else if (dst_port && packet.isUDP()) {
        uint16_t port = ntohs(packet.udpHeader()->dstPort);
        if (!dst_port->contains(port))
            return false;
    }

    return true;
}
